Esercizio 1: Creazione Base e Sincronizzazione con `Join`
Obiettivo: Comprendere come creare, avviare e attendere la terminazione di un thread.
Richieste:
Scrivere un programma che crea due thread
 Il primo thread deve eseguire un metodo che stampa i numeri da 1 a 5, con una pausa di 1 secondo tra ogni numero.
 Il secondo thread deve eseguire un metodo che stampa le lettere dalla 'A' alla 'E', con una pausa di 500 millisecondi tra ogni lettera.
 Il thread principale (`Main`) deve avviare entrambi i thread e poi attendere che entrambi abbiano terminato il loro lavoro prima di stampare un messaggio finale ("Lavoro completato.") e chiudersi.
 Utilizza il metodo `Join()` per garantire che il `Main` aspetti correttamente.

Esercizio 2: Monitoraggio dello Stato di un Thread
Obiettivo: Imparare a controllare lo stato di un thread durante la sua esecuzione.
Richieste:
Scrivere un programma che crea tre thread
 Assegni un nome ad ogni thread (es. Primo, Secondo, Terzo)
 Ogni thread stampa il proprio nome ed esegue un'operazione lunga che dura 10 secondi.
 Il main stampa lo stato di ogni thread e
o se non  "vivo" lo avvia thread.
o Attende 3 secondo.
o Stampa di nuovo lo stato di ogni thread e se  "vivo"
? Usa `Join()` per attendere la sua terminazione.
 Stampa per l'ultima volta lo stato dei thread.
 Analizza e commenta nel codice l'output che ti aspetti di vedere per ogni `Conso-le.WriteLine`.

Esercizio 3: Foreground vs. Background
Obiettivo: Comprendere la differenza fondamentale tra thread in foreground e in back-ground e il loro impatto sulla terminazione dell'applicazione.
Richieste:
Crea due thread, entrambi con un ciclo di lavoro che dura 5 secondi.
 Imposta il primo thread come background.
 Lascia il secondo thread come foreground.
 Il thread principale (`Main`) deve avviare entrambi i thread e poi stampare un mes-saggio ("Il Main ha finito, l'applicazione si chiuder?") e terminare immediatamente, senza usare `Join()`.
 Esegui il programma e osserva il comportamento. Spiega perch l'applicazione rimane aperta e cosa viene stampato a schermo.
 Modifica: Commenta la riga che avvia il thread in foreground. Esegui di nuovo il pro-gramma e descrivi la differenza nel comportamento di chiusura dell'applicazione.
Esercizio 4: Combinazione di Concetti (Manager e Operaio)
Obiettivo: Simulare uno scenario pi complesso in cui un thread "manager" monitora un thread "operaio".
Richieste:
1. Creare un thread operaio (in foreground) che simuli un lavoro di 10 secondi.
2. Creare un thread supervisore (in background) che, ogni 2 secondi, controlli lo stato del thread operaio e stampi un messaggio a console, come: `"[Supervisore]: Lo stato dell'operaio : Running"`.
3. Il thread principale (`Main`) ha il ruolo di "manager":
 Avvia sia il thread operaio che quello supervisore.
 Attende la terminazione solo del thread operaio usando `Join()`.
 Una volta che l'operaio ha finito, il `Main` stampa un messaggio finale ("L'operaio ha finito, il manager chiude la giornata.") e termina.
4. Spiega perch il thread supervisore si arresta automaticamente senza bisogno di un `Join()` specifico.